Skip to content

Publish the experimental React Native chart host - #11

Merged
tannerlinsley merged 5 commits into
mainfrom
taren/react-native-host-poc
Aug 1, 2026
Merged

Publish the experimental React Native chart host#11
tannerlinsley merged 5 commits into
mainfrom
taren/react-native-host-poc

Conversation

@tannerlinsley

@tannerlinsley tannerlinsley commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

  • publish @tanstack/react-native-charts as an experimental package with compiled ESM, declarations, provenance metadata, and react-native conditional exports
  • keep the base host tooltip-free and publish the optional native tooltip from /tooltip
  • add Expo 57 and bare React Native 0.86 fixtures using @tanstack/charts/universal
  • add the adapter to the 12-package fixed release set with a minor changeset
  • verify packed core and native tarballs from clean dependency trees through bare React Native and Expo Metro

Install contract

After the one-time npm bootstrap, a React Native application can install and import the adapter normally:

pnpm add @tanstack/charts @tanstack/react-native-charts d3-scale react-native-svg
import { defineChart, lineY } from '@tanstack/charts/universal'
import { Chart } from '@tanstack/react-native-charts'
import { tooltip } from '@tanstack/react-native-charts/tooltip'

The tested lanes are Expo 57 / React Native 0.86.2 with react-native-svg 15.15.4 and bare React Native 0.86.2 with react-native-svg 15.15.5. The published peer range is React ^19.2.3, React Native ^0.86.0, and react-native-svg >=15.15.4 <16.

The packed-consumer gate installs only checked tarballs, uses each consumer's TypeScript installation, asserts Metro resolves installed dist modules, and bundles iOS and Android through both default Metro configurations. A separate base-entry bundle proves that importing the root does not retain the optional tooltip implementation.

Compatibility and support boundary

Existing browser imports and behavior are unchanged. The browser-first @tanstack/charts root remains the normal web entry; /universal is the cross-runtime barrel, and granular entries remain the bundle-sensitive path.

The workspace Expo 57 fixture boots in Expo Go on an iOS simulator, renders the chart, and logs Hermes bytecode execution. Packed tarballs independently install, typecheck, and compile through bare and Expo iOS/Android pipelines. This remains experimental: packed-artifact device execution, bare-native and Android simulators, physical devices, gesture arbitration, VoiceOver/TalkBack, release builds, performance, and binary-size validation are not support claims.

Bundle evidence

  • React Native SVG host: 26.95 kB minified / 10.22 kB gzip
  • host plus tooltip: 32.71 kB / 12.17 kB; tooltip delta 5.76 kB / 1.94 kB
  • React Native line consumer: 49.18 kB / 19.03 kB
  • full-chart Metro delta over blank: 434.49 KiB minified / 103.00 KiB gzip on iOS; 434.64 KiB / 103.05 KiB on Android
  • /universal over the equivalent granular entry: 119.06 KiB minified / 28.91 KiB gzip / 102 modules on both platforms

Release prerequisite

@tanstack/react-native-charts is still absent from npm. After this PR merges and before the fixed 0.5.0 release publishes:

  • publish the checked react-native-charts-0.4.0.tgz once with public access from hosted CI using a granular npm token and provenance
  • verify the registry integrity and attestations against the checked artifact
  • configure TanStack/charts and .github/workflows/release.yml as the package's trusted publisher

The pending fixed minor changeset then moves all 12 packages to 0.5.0 through the normal OIDC release path.

Validation

  • pnpm validate — full 16-dependency CI graph; 126 test files and 669 tests
  • pnpm benchmark:check
  • pnpm react-native:poc:types
  • pnpm react-native:poc:bundle — ten production Metro bundles across iOS and Android
  • checked bare React Native and Expo packed consumers for types, conditional exports, source boundaries, and iOS/Android bundles
  • clean npm 11 packed-tarball install plus Expo iOS export/Hermes bytecode compilation
  • Expo Go iOS simulator render/Hermes smoke
  • pnpm release:artifacts and pnpm release:check — 12 validated 0.4.0 artifacts

Summary by CodeRabbit

  • New Features
    • Added an experimental React Native SVG charts package.
    • Supports responsive rendering, focus, selection, accessibility actions, and optional native tooltips.
    • Added React Native and Expo examples for interactive line charts.
  • Documentation
    • Added installation, usage, compatibility, and platform support guidance.
    • Documented React Native support as experimental, including current limitations.
  • Chores
    • Added mobile bundle validation, type checks, benchmarks, and consumer verification.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

React Native SVG host

Layer / File(s) Summary
Native renderer and chart behavior
packages/react-native-charts/*
Adds the React Native Chart host, SVG scene renderer, focus model, native paint resolution, focus overlay, and tooltip support with focused tests.
Native packaging and consumer validation
scripts/*, .github/workflows/*, package.json, nx.json
Adds native type checks, packed bare and Expo consumer validation, bundle measurements, source-map checks, and release input tracking.
Examples, benchmarks, and documentation
examples/*, docs/*, README.md, benchmarks/*
Adds React Native and Expo examples, benchmark entries, installation guidance, package documentation, and comparison metadata.
Release integration
.changeset/*, scripts/release-*, scripts/docs-contract.mjs, .gitignore
Adds the package to fixed releases, public documentation checks, release configuration, and Expo file exclusions.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • TanStack/charts#5: Establishes release infrastructure used by this package.
  • TanStack/charts#8: Introduces portable entry points and type boundaries used by the React Native package.
  • TanStack/charts#25: Modifies bundle and packed-consumer validation used by the native checks.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant Chart
  participant ChartRuntime
  participant NativeChartScene
  participant NativeChartTooltip
  App->>Chart: provide chart definition and options
  Chart->>ChartRuntime: create scene and focus state
  ChartRuntime->>NativeChartScene: render ChartScene
  Chart->>NativeChartTooltip: provide focused points and placement options
  NativeChartTooltip-->>Chart: render native tooltip
  NativeChartScene-->>App: display SVG chart
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes publishing the experimental React Native chart host, which is the main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch taren/react-native-host-poc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jul 31, 2026

Copy link
Copy Markdown

@socket-security

socket-security Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/react-native@0.86.2npm/expo@57.0.9npm/@react-native/debugger-frontend@0.86.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.86.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/react-native@0.86.2npm/expo@57.0.9npm/@react-native/debugger-frontend@0.86.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.86.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm fast-xml-parser is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/@react-native-community/cli@20.1.0npm/@react-native-community/cli-platform-android@20.1.0npm/@react-native-community/cli-platform-ios@20.1.0npm/fast-xml-parser@4.5.7

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/fast-xml-parser@4.5.7. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm node-forge is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/expo@57.0.9npm/node-forge@1.4.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/node-forge@1.4.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm fetch-nodeshim

Location: Package overview

From: pnpm-lock.yamlnpm/expo@57.0.9npm/fetch-nodeshim@0.4.10

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/fetch-nodeshim@0.4.10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: npm uuid

Reason: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).

From: pnpm-lock.yamlnpm/expo@57.0.9npm/uuid@7.0.3

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/uuid@7.0.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@tannerlinsley
tannerlinsley force-pushed the taren/portable-barrels branch from ed7bcde to 059faf0 Compare July 31, 2026 18:27
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from f4bc158 to 81ddc12 Compare July 31, 2026 18:46
@tannerlinsley
tannerlinsley changed the base branch from taren/portable-barrels to main July 31, 2026 18:47
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from 6913ceb to 2fa8bbb Compare July 31, 2026 20:31
@tannerlinsley
tannerlinsley changed the base branch from main to taren/universal-chart-barrel July 31, 2026 20:31
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from 2fa8bbb to e14545a Compare July 31, 2026 20:53
@tannerlinsley
tannerlinsley changed the base branch from taren/universal-chart-barrel to taren/universal-compatibility July 31, 2026 20:54
@tannerlinsley
tannerlinsley force-pushed the taren/universal-compatibility branch from ab003d6 to 6e66997 Compare July 31, 2026 20:59
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from e14545a to ef21c79 Compare July 31, 2026 21:14
@tannerlinsley
tannerlinsley force-pushed the taren/universal-compatibility branch from 6e66997 to 952e219 Compare July 31, 2026 21:20
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from ef21c79 to 04cad52 Compare July 31, 2026 21:25
@tannerlinsley
tannerlinsley force-pushed the taren/universal-compatibility branch from 952e219 to 0c743f2 Compare July 31, 2026 21:32
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from 04cad52 to b7e4085 Compare July 31, 2026 21:38
@tannerlinsley
tannerlinsley force-pushed the taren/universal-compatibility branch 3 times, most recently from 521392c to a0fb632 Compare July 31, 2026 22:22
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch 2 times, most recently from 0cd6b8f to 287fdff Compare July 31, 2026 22:43
@tannerlinsley
tannerlinsley changed the base branch from taren/universal-compatibility to main July 31, 2026 22:44
@tannerlinsley tannerlinsley reopened this Jul 31, 2026
@tannerlinsley tannerlinsley changed the title Spike a React Native chart host Publish the experimental React Native chart host Aug 1, 2026
@tannerlinsley
tannerlinsley force-pushed the taren/react-native-host-poc branch from ec4aeb4 to 1d63a11 Compare August 1, 2026 18:35
@tannerlinsley
tannerlinsley marked this pull request as ready for review August 1, 2026 18:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (13)
scripts/check-react-native-types.mjs (1)

27-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

One @types/d3-array ImageData allowlist, two divergent copies. Both gates accept exactly two known strict diagnostics, but they filter tsc output differently, so they can disagree on the same compiler output. Extract one shared helper, for example assertKnownImageDataDiagnostics(output), and call it from both sites.

  • scripts/check-react-native-types.mjs#L27-L42: replace .filter(Boolean) with the error TS filter and call the shared helper.
  • scripts/packed-react-native-consumers.mjs#L293-L306: remove the inline allowlist and call the same shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-react-native-types.mjs` around lines 27 - 42, Extract a shared
assertKnownImageDataDiagnostics(output) helper for the `@types/d3-array` ImageData
allowlist, preserving the requirement of exactly two matching diagnostics. In
scripts/check-react-native-types.mjs:27-42, replace the broad Boolean filtering
with the error TS filter and call the helper; in
scripts/packed-react-native-consumers.mjs:293-306, remove the duplicated inline
allowlist and call the same helper.
scripts/check-packed-consumers.mjs (2)

84-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the two summary loops.

bundles and nativeBundles are formatted with identical code. Concatenate them and print one loop.

♻️ Proposed refactor
   console.log('Packed exports, declarations, and runtime gate passed.')
   console.log('| Consumer | Bytes | Gzip |')
   console.log('| --- | ---: | ---: |')
-  for (const bundle of bundles) {
-    console.log(
-      `| ${bundle.label} | ${formatBytes(bundle.bytes)} | ${formatBytes(bundle.gzip)} |`,
-    )
-  }
-  for (const bundle of nativeBundles) {
+  for (const bundle of [...bundles, ...nativeBundles]) {
     console.log(
       `| ${bundle.label} | ${formatBytes(bundle.bytes)} | ${formatBytes(bundle.gzip)} |`,
     )
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-packed-consumers.mjs` around lines 84 - 96, Merge the separate
summary loops over bundles and nativeBundles into one loop by iterating over
their concatenated collection, while preserving the existing table row
formatting and output order.

307-323: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep native declaration checks enabled with an explicit allowlist.

skipLibCheck: true disables .d.ts error reporting only for the native @tanstack/react-native-charts build, so declaration regressions in react-native, react-native-svg, or @types/* may reach public emitted types. Move the native build back to skipLibCheck: false and constrain types: [] with a small diagnostic allowlist like the @types/d3-array handling elsewhere.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-packed-consumers.mjs` around lines 307 - 323, Update the
TypeScript options object in the native branch of the consumer check to set
skipLibCheck to false and add types: [] so declaration checks remain enabled
without implicit ambient packages. Add the smallest explicit diagnostic
allowlist for the known native dependency errors, following the existing
`@types/d3-array` handling elsewhere in check-packed-consumers.mjs, while
preserving the current options for non-native builds.
packages/react-native-charts/src/interaction.test.ts (1)

14-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the sceneOrder navigation fallback and the remaining focus modes.

These tests exercise focus: 'group-x' and the spatialIndex path. Two branches in interaction.ts stay uncovered:

  • sceneOrder at Lines 147-161. It runs only when resolveFocusStrategy returns undefined, which is the 'nearest' mode and the unset mode. No test calls adjacentFocusPoint on that navigation order, so the x-then-y-then-index sort is unverified.
  • resolveFocusStrategy mappings for 'nearest-y' and 'group-y' at Lines 136-141.

Add a case with focus: 'nearest' that navigates across the points, and assert the mapping for the two remaining modes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/interaction.test.ts` around lines 14 - 52,
Add coverage in interaction.test.ts for the remaining focus behavior: create a
model using focus: 'nearest', call adjacentFocusPoint across its navigation
order, and assert ordering follows x, then y, then index through the sceneOrder
fallback. Also add assertions that resolveFocusStrategy maps 'nearest-y' and
'group-y' to their corresponding strategies, using the existing test helpers and
symbols.
packages/react-native-charts/src/Chart.tsx (2)

219-225: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use the updater form for the pin toggle.

Line 222 reads pinnedKey from the render closure. activate at Line 255 already uses the updater form for the same toggle. Make both paths consistent so the toggle always reads the committed value.

♻️ Proposed change
-    if (sticky) setPinnedKey(pinnedKey ? null : (point?.key ?? null))
+    if (sticky) {
+      setPinnedKey((current) => (current ? null : (point?.key ?? null)))
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Chart.tsx` around lines 219 - 225, Update
the sticky pin toggle in handleResponderRelease to use the setPinnedKey updater
form, matching activate, and derive the next value from the committed previous
key while preserving the existing point?.key fallback and null behavior.

136-140: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not write to refs during render.

Assign callbacks to refs inside a useEffect, or use useEffectEvent for the latest callbacks. This package targets React ^19.2.3, which supports useEffectEvent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Chart.tsx` around lines 136 - 140, Update
the callback-ref assignments in the Chart component to avoid mutating refs
during render: use React’s useEffectEvent for onFocusChange and
onFocusGroupChange, or assign their refs inside a useEffect while preserving
access to the latest callbacks.
packages/react-native-charts/src/Tooltip.tsx (3)

529-543: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

formatValue and chartValueEqual are duplicated across three modules. The package has no shared internal helper module, so each module carries its own copy. The copies can drift, and the Date handling in formatValue already encodes a date-only heuristic that must stay identical in both locations.

  • packages/react-native-charts/src/Tooltip.tsx#L529-L543: move formatValue and chartValueEqual into a new internal module, for example src/format.ts, and import them here.
  • packages/react-native-charts/src/Chart.tsx#L439-L447: delete the local formatValue and import the shared one.
  • packages/react-native-charts/src/interaction.ts#L201-L205: delete the local chartValueEqual and import the shared one.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Tooltip.tsx` around lines 529 - 543, Extract
formatValue and chartValueEqual into a shared internal format module, preserving
the existing Date handling exactly, then import them in Tooltip.tsx. Remove the
duplicate formatValue from packages/react-native-charts/src/Chart.tsx at lines
439-447 and import the shared helper; remove the duplicate chartValueEqual from
packages/react-native-charts/src/interaction.ts at lines 201-205 and import the
shared helper.

327-334: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace the argument spread with a single pass over points.

Math.min(...x) and Math.max(...x) pass one argument per point. A large grouped focus set can exceed the engine argument limit and throw a RangeError. The function also builds two intermediate arrays.

resolveTooltipCoordinate already computes the same value with a loop at Lines 396-405. Use the same approach here for consistency and safety.

♻️ Proposed single-pass bounds
   if (anchor === 'group-center') {
-    const x = points.map((candidate) => candidate.x)
-    const y = points.map((candidate) => candidate.y)
-    return {
-      x: (Math.min(...x) + Math.max(...x)) / 2,
-      y: (Math.min(...y) + Math.max(...y)) / 2,
-    }
+    let minX = point.x
+    let maxX = point.x
+    let minY = point.y
+    let maxY = point.y
+    for (const candidate of points) {
+      minX = Math.min(minX, candidate.x)
+      maxX = Math.max(maxX, candidate.x)
+      minY = Math.min(minY, candidate.y)
+      maxY = Math.max(maxY, candidate.y)
+    }
+    return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Tooltip.tsx` around lines 327 - 334, Update
the group-center branch in resolveTooltipCoordinate to compute x and y bounds in
a single pass over points, avoiding the intermediate arrays and
Math.min/Math.max argument spreads. Reuse the existing loop-based bounds
approach from the later resolveTooltipCoordinate logic, then return the midpoint
of the accumulated minima and maxima.

210-262: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use the shared tooltip content helper here.

packages/charts-core/src/tooltip.ts owns the default content logic, including axis labels and shared-X content. createNativeTooltipContent duplicates that logic and uses generic formatValue for every native tooltip instead of reuse through a core helper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Tooltip.tsx` around lines 210 - 262, Update
createNativeTooltipContent to delegate default tooltip generation to the shared
tooltip content helper from charts-core, preserving the existing custom content
and formatGroup/format handling before delegation. Remove the duplicated
shared-X, axis-label, and generic formatting logic plus
createTooltipContentContext, and pass the scene, points, and primary point
through the core helper so native tooltips use centralized defaults.
packages/react-native-charts/src/Chart.test.tsx (2)

122-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The assertion does not verify the claim in the test name.

renderToStaticMarkup performs a single render, so expect(create).toHaveBeenCalledOnce() only proves that one render calls create one time. It does not prove that the extension avoids singleton identity across component instances.

Render two Chart instances with the same extension and assert that create returns a distinct component for each, or rename the test to state what it checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Chart.test.tsx` around lines 122 - 148,
Update the test named “creates branded native tooltip extensions without
singleton identity” to render two Chart instances using the same customTooltip
extension, then assert create is called for both and returns distinct component
instances. Alternatively, rename the test to accurately describe the
single-render call-count behavior, but preserve the existing extension setup.

15-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Each test file re-invents an incomplete React Native host shim. All three files replace React Native and react-native-svg primitives with DOM tags, and each shim is incomplete in a different way. The shared root cause is the absence of one shared host mock, or of the standard React Native test preset.

  • packages/react-native-charts/src/Chart.test.tsx#L15-L52: extract this MockView into a shared test helper, and forward the dropped props accessibilityActions, onAccessibilityAction, onLayout, onStartShouldSetResponder, onResponderGrant, onResponderMove, onResponderRelease, accessibilityLabel, accessibilityValue, and testID.
  • packages/react-native-charts/src/Tooltip.test.ts#L18-L21: use the shared helper, so the View mock flattens the array style prop and stops producing React warnings.
  • packages/react-native-charts/src/SvgScene.test.tsx#L8-L48: use the shared react-native-svg mock, and assert on the props that the mocked components receive instead of on the serialized DOM attributes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Chart.test.tsx` around lines 15 - 52,
Centralize the incomplete React Native host shims into shared test helpers. In
packages/react-native-charts/src/Chart.test.tsx:15-52, extract MockView and
forward the listed accessibility, responder, layout, and testID props; update
packages/react-native-charts/src/Tooltip.test.ts:18-21 to use it for style
flattening. In packages/react-native-charts/src/SvgScene.test.tsx:8-48, use the
shared react-native-svg mock and assert mocked component props rather than
serialized DOM attributes.
packages/react-native-charts/src/Tooltip.test.ts (2)

36-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for automatic placement selection.

This test pins one explicit placement. It does not cover the 'auto' path, the candidate list ordering, or the overflow comparison loop at Tooltip.tsx Lines 441-456. That loop selects the placement, so it carries the highest risk in placeNativeTooltip.

Add a case with an anchor near the top edge and placement set to 'auto', then assert that the selected placement flips to 'bottom'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Tooltip.test.ts` around lines 36 - 46, Add a
test alongside the existing placeNativeTooltip case that uses an anchor near the
top edge and passes 'auto' as the placement, asserting the returned placement is
'bottom'. Ensure the scenario exercises automatic candidate ordering and
overflow comparison while preserving the expected clamped coordinates.

237-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared ChartScene test fixture.

This scene helper duplicates the helper in interaction.test.ts Lines 99-125. Both build the same 100x60 chart with the same colors, gradients, and theme values. SvgScene.test.tsx repeats most of the same literal.

Move one fixture builder into a shared test helper module, so a required new ChartScene field needs one update instead of three.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Tooltip.test.ts` around lines 237 - 263,
Extract the duplicated scene fixture into a shared test-helper module and update
Tooltip.test.ts, interaction.test.ts, and SvgScene.test.tsx to reuse it.
Preserve the existing 100x60 ChartScene defaults, colors, gradients, and theme
values, and ensure the shared builder accepts points while allowing future
required ChartScene fields to be updated in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/installation.md`:
- Around line 96-100: Update the support-claim sentence in the installation
documentation to replace “are not yet support claims” with clear wording such as
“are outside the current support claims” or “are not currently supported,” while
preserving the listed platform and capability scope.
- Around line 80-84: Update the bare React Native installation command in the
installation documentation to pin react-native-svg to a version within
`@tanstack/react-native-charts`’ peer range, such as ^15.15.4. Leave the Expo
installation command using expo install unchanged.

In `@packages/react-native-charts/README.md`:
- Around line 19-23: Bound all React Native installation and tested-version
references to the declared peer ranges: in
packages/react-native-charts/README.md lines 19-23 use react-native-svg@^15.15.4
(or the tested exact version), in packages/charts-core/docs/installation.md
lines 20-22 include react@^19.2.3, react-native@^0.86.0, and
react-native-svg@^15.15.4, in packages/charts-core/docs/installation.md lines
82-84 use react-native-svg@^15.15.4, and in
packages/react-native-charts/README.md lines 54-56 replace the 15.15.x reference
with >=15.15.4 <16 or 15.15.5.

In `@packages/react-native-charts/src/Chart.test.tsx`:
- Around line 156-166: Configure the Vitest jsdom test environment to set
globalThis.IS_REACT_ACT_ENVIRONMENT to true before tests run, using the
repository’s existing Vitest setup mechanism. Ensure the setup applies to
Chart.test.tsx and other React tests without changing the React.act usage.

In `@packages/react-native-charts/src/Chart.tsx`:
- Around line 172-183: The restore effect around focusModel.restore must avoid
emitting when the restored point is equivalent to the current focused point: use
samePointIdentity from ./interaction before calling commitFocus, while
preserving the existing reset behavior for failed restores. Update the package
README to state that consumers must memoize the definition prop.

In `@packages/react-native-charts/src/SvgScene.tsx`:
- Around line 270-276: Update sanitizeId and the scopedId flow used by
ChartLinearGradient so arbitrary custom gradient ids remain unique after
SVG-safe normalization; replace the lossy character removal with a
collision-free encoding, or validate and reject ids that would collide before
emitting resources. Preserve stable prefix handling while ensuring distinct
inputs such as a.b, a:b, and ab cannot produce the same SVG identifier.

In `@packages/react-native-charts/src/Tooltip.test.ts`:
- Around line 18-21: Update the react-native mock in Tooltip.test.ts to replace
the string View mapping with a MockView component that flattens array style
props before rendering the div, matching the established MockView behavior in
Chart.test.tsx. Keep the Text mock and existing tooltip rendering behavior
unchanged.

In `@scripts/measure-bundles.mjs`:
- Around line 36-52: Update the bundle grouping in the configuration used by
measure-bundles so charts-core/src/svg-renderer.ts and charts-core/src/svg.ts
are assigned to the neutral/native group rather than browser. Add or use a
dedicated browser-only group for rejecting browser-exclusive modules, while
preserving the universal/native boundary required by
measure-react-native-poc.mjs.

---

Nitpick comments:
In `@packages/react-native-charts/src/Chart.test.tsx`:
- Around line 122-148: Update the test named “creates branded native tooltip
extensions without singleton identity” to render two Chart instances using the
same customTooltip extension, then assert create is called for both and returns
distinct component instances. Alternatively, rename the test to accurately
describe the single-render call-count behavior, but preserve the existing
extension setup.
- Around line 15-52: Centralize the incomplete React Native host shims into
shared test helpers. In packages/react-native-charts/src/Chart.test.tsx:15-52,
extract MockView and forward the listed accessibility, responder, layout, and
testID props; update packages/react-native-charts/src/Tooltip.test.ts:18-21 to
use it for style flattening. In
packages/react-native-charts/src/SvgScene.test.tsx:8-48, use the shared
react-native-svg mock and assert mocked component props rather than serialized
DOM attributes.

In `@packages/react-native-charts/src/Chart.tsx`:
- Around line 219-225: Update the sticky pin toggle in handleResponderRelease to
use the setPinnedKey updater form, matching activate, and derive the next value
from the committed previous key while preserving the existing point?.key
fallback and null behavior.
- Around line 136-140: Update the callback-ref assignments in the Chart
component to avoid mutating refs during render: use React’s useEffectEvent for
onFocusChange and onFocusGroupChange, or assign their refs inside a useEffect
while preserving access to the latest callbacks.

In `@packages/react-native-charts/src/interaction.test.ts`:
- Around line 14-52: Add coverage in interaction.test.ts for the remaining focus
behavior: create a model using focus: 'nearest', call adjacentFocusPoint across
its navigation order, and assert ordering follows x, then y, then index through
the sceneOrder fallback. Also add assertions that resolveFocusStrategy maps
'nearest-y' and 'group-y' to their corresponding strategies, using the existing
test helpers and symbols.

In `@packages/react-native-charts/src/Tooltip.test.ts`:
- Around line 36-46: Add a test alongside the existing placeNativeTooltip case
that uses an anchor near the top edge and passes 'auto' as the placement,
asserting the returned placement is 'bottom'. Ensure the scenario exercises
automatic candidate ordering and overflow comparison while preserving the
expected clamped coordinates.
- Around line 237-263: Extract the duplicated scene fixture into a shared
test-helper module and update Tooltip.test.ts, interaction.test.ts, and
SvgScene.test.tsx to reuse it. Preserve the existing 100x60 ChartScene defaults,
colors, gradients, and theme values, and ensure the shared builder accepts
points while allowing future required ChartScene fields to be updated in one
place.

In `@packages/react-native-charts/src/Tooltip.tsx`:
- Around line 529-543: Extract formatValue and chartValueEqual into a shared
internal format module, preserving the existing Date handling exactly, then
import them in Tooltip.tsx. Remove the duplicate formatValue from
packages/react-native-charts/src/Chart.tsx at lines 439-447 and import the
shared helper; remove the duplicate chartValueEqual from
packages/react-native-charts/src/interaction.ts at lines 201-205 and import the
shared helper.
- Around line 327-334: Update the group-center branch in
resolveTooltipCoordinate to compute x and y bounds in a single pass over points,
avoiding the intermediate arrays and Math.min/Math.max argument spreads. Reuse
the existing loop-based bounds approach from the later resolveTooltipCoordinate
logic, then return the midpoint of the accumulated minima and maxima.
- Around line 210-262: Update createNativeTooltipContent to delegate default
tooltip generation to the shared tooltip content helper from charts-core,
preserving the existing custom content and formatGroup/format handling before
delegation. Remove the duplicated shared-X, axis-label, and generic formatting
logic plus createTooltipContentContext, and pass the scene, points, and primary
point through the core helper so native tooltips use centralized defaults.

In `@scripts/check-packed-consumers.mjs`:
- Around line 84-96: Merge the separate summary loops over bundles and
nativeBundles into one loop by iterating over their concatenated collection,
while preserving the existing table row formatting and output order.
- Around line 307-323: Update the TypeScript options object in the native branch
of the consumer check to set skipLibCheck to false and add types: [] so
declaration checks remain enabled without implicit ambient packages. Add the
smallest explicit diagnostic allowlist for the known native dependency errors,
following the existing `@types/d3-array` handling elsewhere in
check-packed-consumers.mjs, while preserving the current options for non-native
builds.

In `@scripts/check-react-native-types.mjs`:
- Around line 27-42: Extract a shared assertKnownImageDataDiagnostics(output)
helper for the `@types/d3-array` ImageData allowlist, preserving the requirement
of exactly two matching diagnostics. In
scripts/check-react-native-types.mjs:27-42, replace the broad Boolean filtering
with the error TS filter and call the helper; in
scripts/packed-react-native-consumers.mjs:293-306, remove the duplicated inline
allowlist and call the same helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 080d04bd-6a0d-48cc-8423-4f698103757d

📥 Commits

Reviewing files that changed from the base of the PR and between ff55584 and 1d63a11.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (71)
  • .changeset/README.md
  • .changeset/config.json
  • .changeset/native-svg-host.md
  • .github/workflows/chart-library-benchmarks.yml
  • .gitignore
  • API-FRICTION.md
  • NATIVE-PLATFORM-SUPPORT-SPIKE.md
  • REACT-NATIVE-HOST-POC.md
  • README.md
  • benchmarks/comparison/bundle-baseline.json
  • benchmarks/entries/charts-react-native-line.ts
  • benchmarks/entries/charts-react-native-tooltip.ts
  • benchmarks/entries/charts-react-native.ts
  • docs/comparison.md
  • docs/installation.md
  • docs/overview.md
  • examples/charts-expo/App.tsx
  • examples/charts-expo/app.json
  • examples/charts-expo/index.ts
  • examples/charts-expo/metro.config.cjs
  • examples/charts-expo/package.json
  • examples/charts-expo/tsconfig.json
  • examples/charts-react-native/App.granular.tsx
  • examples/charts-react-native/App.tsx
  • examples/charts-react-native/RevenueChart.tsx
  • examples/charts-react-native/app.json
  • examples/charts-react-native/babel.config.cjs
  • examples/charts-react-native/index.blank.js
  • examples/charts-react-native/index.core.js
  • examples/charts-react-native/index.granular.js
  • examples/charts-react-native/index.js
  • examples/charts-react-native/index.svg.js
  • examples/charts-react-native/metro.config.cjs
  • examples/charts-react-native/package.json
  • examples/charts-react-native/tsconfig.json
  • examples/charts-react-native/tsconfig.strict.json
  • nx.json
  • package.json
  • packages/charts-core/docs/comparison.md
  • packages/charts-core/docs/installation.md
  • packages/charts-core/docs/overview.md
  • packages/charts-core/src/reconcile.test.ts
  • packages/react-native-charts/CHANGELOG.md
  • packages/react-native-charts/LICENSE
  • packages/react-native-charts/README.md
  • packages/react-native-charts/package.json
  • packages/react-native-charts/src/Chart.test.tsx
  • packages/react-native-charts/src/Chart.tsx
  • packages/react-native-charts/src/FocusOverlay.tsx
  • packages/react-native-charts/src/SvgScene.test.tsx
  • packages/react-native-charts/src/SvgScene.tsx
  • packages/react-native-charts/src/Tooltip.test.ts
  • packages/react-native-charts/src/Tooltip.tsx
  • packages/react-native-charts/src/index.native.ts
  • packages/react-native-charts/src/index.ts
  • packages/react-native-charts/src/interaction.test.ts
  • packages/react-native-charts/src/interaction.ts
  • packages/react-native-charts/src/paint.test.ts
  • packages/react-native-charts/src/paint.ts
  • packages/react-native-charts/src/tooltip-entry.native.ts
  • packages/react-native-charts/src/tooltip-entry.ts
  • packages/react-native-charts/tsconfig.json
  • scripts/check-packed-consumers.mjs
  • scripts/check-react-native-types.mjs
  • scripts/docs-contract.mjs
  • scripts/measure-bundles.mjs
  • scripts/measure-react-native-poc.mjs
  • scripts/packed-react-native-consumers.mjs
  • scripts/release-package-config.mjs
  • scripts/release-workflow.test.mjs
  • scripts/sync-release-version.mjs

Comment thread docs/installation.md
Comment on lines +80 to +84
Bare React Native 0.86 applications install the renderer directly:

```sh
pnpm add @tanstack/charts @tanstack/react-native-charts d3-scale react-native-svg
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

jq -r '.peerDependencies["react-native-svg"]' packages/react-native-charts/package.json
rg -n -C 2 'react-native-svg' docs/installation.md examples/charts-react-native/package.json examples/charts-expo/package.json
printf 'Unqualified registry version: '
pnpm view react-native-svg version

Repository: TanStack/charts

Length of output: 2855


Pin react-native-svg in the bare React Native install command.

@tanstack/react-native-charts requires react-native-svg >=15.15.4 <16, but the bare command installs the unqualified latest, which can resolve an incompatible major version. Use a version specifier that stays in the declared peer range, such as react-native-svg@^15.15.4, and leave Expo installs with expo install so Expo picks its compatible native version.

Suggested documentation fix
- pnpm add `@tanstack/charts` `@tanstack/react-native-charts` d3-scale react-native-svg
+ pnpm add `@tanstack/charts` `@tanstack/react-native-charts` d3-scale react-native-svg@^15.15.4
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Bare React Native 0.86 applications install the renderer directly:
```sh
pnpm add @tanstack/charts @tanstack/react-native-charts d3-scale react-native-svg
```
Bare React Native 0.86 applications install the renderer directly:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/installation.md` around lines 80 - 84, Update the bare React Native
installation command in the installation documentation to pin react-native-svg
to a version within `@tanstack/react-native-charts`’ peer range, such as ^15.15.4.
Leave the Expo installation command using expo install unchanged.

Comment thread docs/installation.md
Comment on lines +96 to +100
Packed tarballs are typechecked and bundled through default bare React Native
and Expo Metro configurations on iOS and Android. The workspace Expo 57
fixture also renders in Expo Go on an iOS simulator. Bare-native and Android
simulators, physical devices, gestures, visual parity, and screen readers are
not yet support claims.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the support-claim wording.

The sentence says are not yet support claims. Replace it with are outside the current support claims or are not currently supported. The current wording is ungrammatical and ambiguous for users.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/installation.md` around lines 96 - 100, Update the support-claim
sentence in the installation documentation to replace “are not yet support
claims” with clear wording such as “are outside the current support claims” or
“are not currently supported,” while preserving the listed platform and
capability scope.

Comment on lines +19 to +23
Bare React Native applications can install it directly:

```sh
npm install react-native-svg
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n -C2 'react-native-svg|react-native@|react@|15\.15' \
  packages/react-native-charts/package.json \
  packages/react-native-charts/README.md \
  packages/charts-core/docs/installation.md

Repository: TanStack/charts

Length of output: 4043


Bound React Native installation commands with the declared peer ranges.

@tanstack/react-native-charts declares react ^19.2.3, react-native ^0.86.0, and react-native-svg >=15.15.4 <16. Update the bare install commands and tested-version range so users do not get broader or incompatible versions.

  • packages/react-native-charts/README.md#L19-L23: Use npm install react-native-svg@^15.15.4 or the tested exact version.
  • packages/charts-core/docs/installation.md#L20-L22: Use pnpm add @tanstack/react-native-charts react@^19.2.3 react-native@^0.86.0 react-native-svg@^15.15.4.
  • packages/charts-core/docs/installation.md#L82-L84: Use pnpm add @tanstack/charts @tanstack/react-native-charts d3-scale react-native-svg@^15.15.4.
  • packages/react-native-charts/README.md#L54-L56: Replace react-native-svg 15.15.x with >=15.15.4 <16 or 15.15.5.
📍 Affects 2 files
  • packages/react-native-charts/README.md#L19-L23 (this comment)
  • packages/charts-core/docs/installation.md#L20-L22
  • packages/charts-core/docs/installation.md#L82-L84
  • packages/react-native-charts/README.md#L54-L56
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/README.md` around lines 19 - 23, Bound all React
Native installation and tested-version references to the declared peer ranges:
in packages/react-native-charts/README.md lines 19-23 use
react-native-svg@^15.15.4 (or the tested exact version), in
packages/charts-core/docs/installation.md lines 20-22 include react@^19.2.3,
react-native@^0.86.0, and react-native-svg@^15.15.4, in
packages/charts-core/docs/installation.md lines 82-84 use
react-native-svg@^15.15.4, and in packages/react-native-charts/README.md lines
54-56 replace the 15.15.x reference with >=15.15.4 <16 or 15.15.5.

Comment on lines +156 to +166
await React.act(() => {
root.render(
<Chart
definition={definition}
accessibilityLabel="Revenue"
width={480}
height={260}
onFocusChange={onFocusChange}
/>,
)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Look for the React act environment flag and the Vitest setup for the package.
set -euo pipefail

rg -n 'IS_REACT_ACT_ENVIRONMENT'
fd -t f 'vitest.config' -x cat -n {}
fd -t f 'vitest.workspace' -x cat -n {}

Repository: TanStack/charts

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -u
echo "== tracked files matching react-native-charts =="
git ls-files | rg 'packages/react-native-charts/.+|vitest\.config|vitest\.workspace|setup|vite\.config' || true

echo "== package files =="
for p in package.json packages/react-native-charts/package.json; do
  if [ -f "$p" ]; then
    echo "--- $p"
    cat -n "$p"
  fi
done

echo "== search act flag variants =="
rg -n --hidden --no-regex-esc 'IS_REACT_ACT_ENVIRONMENT|`@testing-library/setup`|actEnv|env\.' . || true

echo "== vitest config/workspace files =="
while IFS= read -r f; do
  echo "--- $f"
  cat -n "$f"
done < <(git ls-files | rg '(^|/)vitest\.(config|workspace)(\..+)?|(setup.*test|.*test.*setup)\.(js|ts|mjs|cts|mcts|jsx|tsx)$' || true)

Repository: TanStack/charts

Length of output: 10617


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== react-native chart test header =="
sed -n '1,80p' packages/react-native-charts/src/Chart.test.tsx | cat -n

echo
echo "== vitest config =="
cat -n vitest.config.ts

echo
echo "== setup files =="
git ls-files | rg '(^|/)setup.*test\.|.*test.*setup\.|packages/react-native-charts/src/.+\.test\.(ts|tsx)$' | sort
for f in $(git ls-files | rg '(^|/)setup.*test\.|.*test.*setup\.|packages/react-native-charts/src/.+\.test\.(ts|tsx)$'); do
  echo "--- $f"
  sed -n '1,220p' "$f" | cat -n
done

echo
echo "== act flag references using grep instead of rg flagged option =="
grep -RIn 'IS_REACT_ACT_ENVIRONMENT' . || true

echo
echo "== files with act imports/usages =="
grep -RIn --include='*.ts' --include='*.tsx' --include='*.js' --include='*.mjs' '\bReact\.act\b|\bimport .*act\b' . || true

Repository: TanStack/charts

Length of output: 43356


Set the React act environment flag.

packages/react-native-charts/src/Chart.test.tsx uses React.act, but no repository setup sets globalThis.IS_REACT_ACT_ENVIRONMENT = true, and the Vitest config does not configure Testing Library setup. Add an environment setup that sets the flag for Vitest jsdom tests to avoid React act warnings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Chart.test.tsx` around lines 156 - 166,
Configure the Vitest jsdom test environment to set
globalThis.IS_REACT_ACT_ENVIRONMENT to true before tests run, using the
repository’s existing Vitest setup mechanism. Ensure the setup applies to
Chart.test.tsx and other React tests without changing the React.act usage.

Comment on lines +172 to +183
React.useEffect(() => {
const previous = focusedPointsRef.current[0]
if (!focusModel || !previous) return
const restored = focusModel.restore(previous)
if (restored) {
setFocusSource('restored')
commitFocus(focusModel.group(restored))
} else {
setPinnedKey(null)
commitFocus([])
}
}, [commitFocus, focusModel])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

An unmemoized definition prop can drive a render loop.

The chain is: definition identity → scene (Line 127) → focusModel (Line 131) → this effect. The effect calls focusModel.restore, which returns a point from the new scene.points. commitFocus compares by reference, so the references always differ, and it calls setFocusedPoints plus onFocusChange.

If a consumer creates definition inline and also updates parent state from onFocusChange, each focus change recreates definition, which recreates the scene and refires this effect. The result is a sustained update loop.

Add a guard so the restore path does not emit when the restored point is equivalent to the current focus, for example by comparing with samePointIdentity from ./interaction before it calls commitFocus. Also state the memoization requirement for definition in the package README.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Chart.tsx` around lines 172 - 183, The
restore effect around focusModel.restore must avoid emitting when the restored
point is equivalent to the current focused point: use samePointIdentity from
./interaction before calling commitFocus, while preserving the existing reset
behavior for failed restores. Update the package README to state that consumers
must memoize the definition prop.

Comment on lines +270 to +276
function scopedId(prefix: string, id: string) {
return prefix ? `${prefix}-${sanitizeId(id)}` : sanitizeId(id)
}

function sanitizeId(value: string) {
return value.replaceAll(/[^a-zA-Z0-9_-]/g, '')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the public scene contract before tracing gradient producers.
ast-grep outline packages/charts-core/src/types.ts --match ChartScene --view expanded

# Find gradient construction and custom-scene extension paths.
rg -n -P -C 5 '\bgradients\s*:' packages/charts-core/src packages/react-native-charts/src
rg -n -P -C 5 '\bid\s*:\s*[^,]+' packages/charts-core/src packages/react-native-charts/src

# Demonstrate the collision without executing repository code.
node - <<'NODE'
const sanitizeId = (value) => value.replaceAll(/[^a-zA-Z0-9_-]/g, '')
for (const value of ['a.b', 'a:b', 'ab']) {
  console.log(`${value} -> ${sanitizeId(value)}`)
}
NODE

Repository: TanStack/charts

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant type definitions =="
rg -n -C 4 'interface ChartLinearGradient|type ChartLinearGradient|id:\s*string|gradients:' packages/charts-core/src/types.ts packages/react-native-charts/src types packages -g '*.ts' -g '*.tsx' | head -200

echo
echo "== SvgScene gradient handling =="
rg -n -C 8 'scopedId|sanitizeId|gradients|linearGradient|gradientDef|fill="url|stroke="url|url\(#' packages/react-native-charts/src/SvgScene.tsx

echo
echo "== core gradient construction and paint references =="
rg -n -C 5 'gradients|linearGradient|ChartLinearGradient|url\(#|fill:|stroke:' packages/charts-core/src -g '*.ts' | sed -n '1,260p'

Repository: TanStack/charts

Length of output: 12608


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SvgScene relevant sections =="
sed -n '1,330p' packages/react-native-charts/src/SvgScene.tsx

echo
echo "== d3 svg-resources sanitization for comparison =="
sed -n '1,100p' packages/charts-core-d3/src/svg-resources.ts

echo
echo "== gradient paint references in react-native and d3 renderers =="
rg -n -C 6 'url\(#|gradient|gradientDef|ChartLinearGradient|gradients' packages/charts-core-d3/src packages/react-native-charts/src packages/charts-core/src --glob '*.ts' --glob '*.tsx' | sed -n '1,260p'

echo
echo "== TypeScript type of ChartLinearGradient =="
sed -n '390,425p' packages/charts-core/src/types.ts

Repository: TanStack/charts

Length of output: 31137


Prevent sanitized SVG identifier collisions.

sanitizeId is non-injective, so different values such as a.b, a:b, and ab can produce the same SVG id. A ChartLinearGradient accepts arbitrary id strings, so collision-prone custom scenes can emit gradients that render under one shared SVG resource. Use a collision-free encoding or validate gradient ids before this renderer emits them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/SvgScene.tsx` around lines 270 - 276, Update
sanitizeId and the scopedId flow used by ChartLinearGradient so arbitrary custom
gradient ids remain unique after SVG-safe normalization; replace the lossy
character removal with a collision-free encoding, or validate and reject ids
that would collide before emitting resources. Preserve stable prefix handling
while ensuring distinct inputs such as a.b, a:b, and ab cannot produce the same
SVG identifier.

Comment on lines +18 to +21
vi.mock('react-native', () => ({
Text: 'span',
View: 'div',
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The react-native mock does not flatten array styles.

NativeChartTooltip passes style={[tooltipStyle, { left, top }]} at Tooltip.tsx Line 131. This mock maps View to the DOM tag 'div', so react-dom receives an array for the style prop. react-dom treats the array as a plain object and warns about the numeric keys as unsupported style properties.

Chart.test.tsx already flattens array styles in its MockView at Lines 23-25. Apply the same flattening here, so the tests do not depend on tolerated React warnings.

🧪 Proposed mock with style flattening
-vi.mock('react-native', () => ({
-  Text: 'span',
-  View: 'div',
-}))
+vi.mock('react-native', async () => {
+  const ReactModule = await import('react')
+  const flatten = (style: unknown) =>
+    Array.isArray(style)
+      ? Object.assign({}, ...style.filter(Boolean))
+      : style
+  return {
+    Text: 'span',
+    View: ({ style, children, ...rest }: Record<string, unknown>) =>
+      ReactModule.createElement(
+        'div',
+        { ...rest, style: flatten(style) },
+        children as React.ReactNode,
+      ),
+  }
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mock('react-native', () => ({
Text: 'span',
View: 'div',
}))
vi.mock('react-native', async () => {
const ReactModule = await import('react')
const flatten = (style: unknown) =>
Array.isArray(style)
? Object.assign({}, ...style.filter(Boolean))
: style
return {
Text: 'span',
View: ({ style, children, ...rest }: Record<string, unknown>) =>
ReactModule.createElement(
'div',
{ ...rest, style: flatten(style) },
children as React.ReactNode,
),
}
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-native-charts/src/Tooltip.test.ts` around lines 18 - 21,
Update the react-native mock in Tooltip.test.ts to replace the string View
mapping with a MockView component that flattens array style props before
rendering the div, matching the established MockView behavior in Chart.test.tsx.
Keep the Text mock and existing tooltip rendering behavior unchanged.

Comment on lines +36 to +52
browser: [
'packages/charts-core/src/adapter.ts',
'packages/charts-core/src/adapter-renderer.ts',
'packages/charts-core/src/canvas.ts',
'packages/charts-core/src/dom.ts',
'packages/charts-core/src/dom-text.ts',
'packages/charts-core/src/export.ts',
'packages/charts-core/src/reconcile.ts',
'packages/charts-core/src/renderer.ts',
'packages/charts-core/src/svg-renderer.ts',
'packages/charts-core/src/svg-resources.ts',
'packages/charts-core/src/svg-surface.ts',
'packages/charts-core/src/svg.ts',
'packages/react-charts/src/CanvasChart.tsx',
'packages/react-charts/src/Chart.tsx',
'packages/react-charts/src/RendererChart.tsx',
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check what the native benchmark entries import.
set -euo pipefail

fd -t f 'charts-react-native*.ts' benchmarks/entries --exec cat -n
rg -n "svg-renderer|src/svg\.ts|universal" scripts/measure-bundles.mjs scripts/measure-react-native-poc.mjs

Repository: TanStack/charts

Length of output: 2342


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- measure-bundles relevant slices ---\n'
sed -n '1,90p' scripts/measure-bundles.mjs
sed -n '340,430p' scripts/measure-bundles.mjs
sed -n '960,1025p' scripts/measure-bundles.mjs

printf '\n--- measure-react-native-poc relevant slices ---\n'
sed -n '1,180p' scripts/measure-react-native-poc.mjs

printf '\n--- package entry files ---\n'
fd -t f 'package.json|universal.*|react-native-chart'|xargs -r -I{} sh -c 'echo "### {}"; sed -n "1,220p" "{}"' | sed -n '1,260p'

printf '\n--- imports / exports around charts-core svg and universal ---\n'
rg -n "svg-renderer|from ['\"]\.\/svg|export .*svg|from ['\"]@\tanstack/charts/universal|from ['\"]`@tanstack/react-native-charts`|export \{ Chart \}" packages benchmarks scripts -g '*.ts' -g '*.tsx' -g '*.mjs' -g '*.json' | sed -n '1,240p'

Repository: TanStack/charts

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- measure-bundles relevant slices ---'
sed -n '1,90p' scripts/measure-bundles.mjs
sed -n '340,430p' scripts/measure-bundles.mjs
sed -n '960,1025p' scripts/measure-bundles.mjs

printf '%s\n' ''
printf '%s\n' '--- measure-react-native-poc relevant slices ---'
sed -n '1,180p' scripts/measure-react-native-poc.mjs

printf '%s\n' ''
printf '%s\n' '--- imports / exports around charts-core svg and universal ---'
rg -n "svg-renderer|from ['\"]\.\/svg|export .*svg|from ['\"]@\tanstack/charts/universal|from ['\"]`@tanstack/react-native-charts`|export \{ Chart \}" packages benchmarks scripts -g '*.ts' -g '*.tsx' -g '*.mjs' -g '*.json' | sed -n '1,240p'

Repository: TanStack/charts

Length of output: 20943


Keep the native SVG boundary aligned with the universal entry boundary.

scripts/measure-react-native-poc.mjs requires packages/charts-core/src/svg-renderer.ts and packages/charts-core/src/svg.ts in the native universal bundle, but scripts/measure-bundles.mjs treats those same modules as browser. A native-boundary bundle reachable through @tanstack/charts/universal will cross the browser group and fail while still needing the static SVG serializer. Move these shared serializer modules to a neutral/native group and reject them only from a dedicated browser group.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/measure-bundles.mjs` around lines 36 - 52, Update the bundle grouping
in the configuration used by measure-bundles so charts-core/src/svg-renderer.ts
and charts-core/src/svg.ts are assigned to the neutral/native group rather than
browser. Add or use a dedicated browser-only group for rejecting
browser-exclusive modules, while preserving the universal/native boundary
required by measure-react-native-poc.mjs.

@tannerlinsley
tannerlinsley merged commit f7d041f into main Aug 1, 2026
19 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant